# app/services/spider_service.py

import os
import shutil
from typing import Tuple, Optional
import docker
from datetime import datetime
import pytz # Use timezone-aware datetimes

from app.core.config import settings
from app.crud.crud_spider import crud_spider
from app.crud.crud_job import crud_job
from app.db.models import JobStatus, JobType, Spider # Added JobType, Spider
from app.db.session import SessionLocal

# No need to instantiate crud_spider here if it's used directly

class SpiderService:
    def __init__(self):
        try:
            self.docker_client = docker.from_env()
            self.docker_client.ping()
            print("Docker client initialized successfully for SpiderService.")
        except Exception as e:
            print(f"Error initializing Docker client for SpiderService: {e}")
            self.docker_client = None

    def ensure_spider_directory(self, spider_id: str) -> str:
        """确保爬虫目录存在并返回路径"""
        spider_dir = os.path.join(settings.SPIDERS_DIR, spider_id)
        os.makedirs(spider_dir, exist_ok=True)
        return spider_dir

    def save_spider_file(self, spider_id: str, file_content: bytes, filename: str) -> str:
        """保存爬虫文件"""
        spider_dir = self.ensure_spider_directory(spider_id)
        file_path = os.path.join(spider_dir, filename)
        try:
            with open(file_path, "wb") as f:
                f.write(file_content)
            print(f"File saved to {file_path}")
            return file_path
        except Exception as e:
            print(f"Error saving file to {file_path}: {e}")
            raise # Re-raise the exception

    def read_spider_file(self, file_path: str) -> Optional[str]:
        """读取爬虫文件内容"""
        if not os.path.exists(file_path):
            return None
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                return f.read()
        except Exception as e:
            print(f"Error reading file {file_path}: {e}")
            return None # Or raise error

    def delete_spider_directory(self, spider_id: str) -> bool:
        """删除爬虫目录"""
        spider_dir = os.path.join(settings.SPIDERS_DIR, spider_id)
        if os.path.exists(spider_dir):
            try:
                shutil.rmtree(spider_dir)
                print(f"Spider directory {spider_dir} deleted.")
                return True
            except Exception as e:
                print(f"Error deleting directory {spider_dir}: {e}")
                return False
        return False # Directory didn't exist

    def install_dependencies(self, spider_id: str, job_id: str) -> None:
        """安装爬虫依赖 (JobType should be DEPENDENCY_INSTALL)"""
        if not self.docker_client:
            print(f"Docker client not available. Cannot install dependencies for job {job_id}.")
            # Update job status to FAILED
            db = None
            try:
                db = SessionLocal()
                job = crud_job.get(db, id=job_id)
                if job:
                    crud_job.update(db, db_obj=job, obj_in={
                        "status": JobStatus.FAILED,
                        "error_message": "Docker client not available for dependency installation.",
                        "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                    })
                    db.commit()
            finally:
                if db:
                    db.close()
            return

        print(f"Starting dependency installation for job {job_id} (Spider: {spider_id})")
        db = None
        job = None
        container = None

        # <<< MODIFIED: Move db session creation inside try >>>
        try:
            db = SessionLocal()
            spider = crud_spider.get(db, id=spider_id)
            job = crud_job.get(db, id=job_id) # Assume job already exists

            if not job:
                 print(f"Job {job_id} not found for dependency installation.")
                 return

            if not spider or not spider.dependencies_path or not os.path.exists(spider.dependencies_path):
                error_msg = f"依赖文件不存在或路径无效: {spider.dependencies_path if spider else 'Spider not found'}"
                print(error_msg)
                crud_job.update(
                    db,
                    db_obj=job,
                    obj_in={
                        "status": JobStatus.FAILED,
                        "error_message": error_msg,
                        "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                    }
                )
                db.commit()
                return

            # 更新任务状态为运行中
            crud_job.update(
                db,
                db_obj=job,
                obj_in={
                    "status": JobStatus.RUNNING,
                    "started_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                }
            )
            db.commit() # Commit status update

            # --- Docker Container Execution for pip install ---
            spider_dir = os.path.dirname(spider.dependencies_path)
            requirements_filename = os.path.basename(spider.dependencies_path) # e.g., requirements.txt

            # Define log path for dependency installation job
            log_dir = os.path.join(settings.LOGS_DIR, "jobs", spider_id)
            os.makedirs(log_dir, exist_ok=True)
            log_path = os.path.join(log_dir, f"job_{job_id}_deps.log")
            crud_job.update(db, db_obj=job, obj_in={"log_path": log_path}) # Save log path
            db.commit()

            try:
                print(f"Running container for dependency install job {job_id}...")
                # <<< MODIFIED: Change volume mode to 'ro' >>>
                # Mount the source directory read-only to prevent pip from writing back caches/build artifacts.
                # If pip needs a writable cache, consider mapping a separate Docker volume to /root/.cache/pip inside the container.
                container = self.docker_client.containers.run(
                    settings.DOCKER_BASE_IMAGE,
                    command=f"pip install -r /app/{requirements_filename}", # Use dynamic filename
                    volumes={
                        spider_dir: {'bind': '/app', 'mode': 'ro'} # READ-ONLY mount
                        # Example cache volume: 'pip-cache:/root/.cache/pip' (ensure 'pip-cache' volume exists)
                    },
                    network=settings.DOCKER_NETWORK,
                    detach=True,
                    remove=False # Keep container until logs fetched
                )
                # <<< END MODIFIED >>>
                print(f"Dependency install container {container.id[:12]} started for job {job_id}.")

                # 更新容器ID
                crud_job.update(db, db_obj=job, obj_in={"container_id": container.id})
                db.commit()

                # 等待容器完成
                result = container.wait()
                exit_code = result.get('StatusCode', -1)
                print(f"Dependency install container {container.id[:12]} finished with status code {exit_code}.")

                # <<< MODIFIED: Get logs BEFORE removing container >>>
                logs = ""
                try:
                    logs = container.logs(stdout=True, stderr=True).decode('utf-8', errors='replace')
                    print(f"Retrieved {len(logs)} bytes of logs for dep install container {container.id[:12]}.")
                except Exception as log_err:
                     print(f"Error retrieving logs for dep install container {container.id[:12]}: {log_err}")
                     logs = f"Error retrieving logs: {log_err}"

                # 保存日志
                try:
                    with open(log_path, "w", encoding="utf-8") as f:
                        f.write(logs)
                    print(f"Dependency install logs saved to {log_path}")
                except Exception as file_err:
                     print(f"Error saving dependency install logs to {log_path}: {file_err}")
                # <<< END MODIFIED >>>


                # 更新最终任务状态
                completed_at = datetime.now(pytz.timezone(settings.TIMEZONE))
                final_status_data = {
                    "exit_code": exit_code,
                    "completed_at": completed_at
                }
                if exit_code == 0:
                    final_status_data["status"] = JobStatus.COMPLETED
                    print(f"Dependency installation job {job_id} completed successfully.")
                else:
                    final_status_data["status"] = JobStatus.FAILED
                    log_tail = "\n".join(logs.splitlines()[-20:]) # Get last 20 lines for error context
                    final_status_data["error_message"] = f"安装依赖失败，退出代码: {exit_code}. Log Tail:\n{log_tail}"
                    print(f"Dependency installation job {job_id} failed with exit code {exit_code}.")

                crud_job.update(db, db_obj=job, obj_in=final_status_data)
                db.commit()

            except docker.errors.ImageNotFound:
                error_msg = f"Docker image not found for deps install: {settings.DOCKER_BASE_IMAGE}"
                print(error_msg)
                crud_job.update(db, db_obj=job, obj_in={"status": JobStatus.FAILED, "error_message": error_msg, "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))})
                db.commit()
            except docker.errors.APIError as api_err:
                 error_msg = f"Docker API error during deps install: {api_err}"
                 print(error_msg)
                 crud_job.update(db, db_obj=job, obj_in={"status": JobStatus.FAILED, "error_message": error_msg, "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))})
                 db.commit()
            except Exception as run_err:
                error_msg = f"安装依赖容器时发生未知错误: {str(run_err)}"
                print(error_msg)
                crud_job.update(
                    db,
                    db_obj=job,
                    obj_in={
                        "status": JobStatus.FAILED,
                        "error_message": error_msg,
                        "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                    }
                )
                db.commit()
            finally:
                # 清理容器
                if container:
                    try:
                        container.remove()
                        print(f"Dependency install container {container.id[:12]} removed.")
                    except docker.errors.NotFound:
                         print(f"Dependency install container {container.id[:12]} already removed.")
                    except Exception as remove_err:
                        print(f"Error removing dependency install container {container.id[:12]}: {remove_err}")

        except Exception as e:
             error_msg_outer = f"运行依赖安装服务时发生错误: {str(e)}"
             print(error_msg_outer)
             if db and job:
                 try:
                     crud_job.update(
                         db,
                         db_obj=job,
                         obj_in={
                             "status": JobStatus.FAILED,
                             "error_message": error_msg_outer,
                             "completed_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                         }
                     )
                     db.commit()
                 except Exception as db_update_err:
                      print(f"Failed to update job status after outer error in deps install: {db_update_err}")
        finally:
            # <<< MODIFIED: Ensure db connection is closed >>>
            if db:
                db.close()
                print(f"Database session closed for dependency job {job_id}.")
            # <<< END MODIFIED >>>


# Instantiate the service
spider_service = SpiderService()